# ===============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of eos.
#
# eos is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# eos is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with eos.  If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================

import re
from collections import OrderedDict

from sqlalchemy.orm import reconstructor

import eos.db


# Order is significant here - UI uses order as-is for built-in patterns
BUILTINS = OrderedDict([
    (-1, ('全抗', 25, 25, 25, 25)),
    (-2, ('[通用]电磁抗性', 1, 0, 0, 0)),
    (-3, ('[通用]热能抗性', 0, 1, 0, 0)),
    (-4, ('[通用]动能抗性', 0, 0, 1, 0)),
    (-5, ('[通用]爆炸抗性', 0, 0, 0, 1)),
    (-6, ('[频率晶体]|[T2] 极光', 5, 3, 0, 0)),
    (-7, ('[频率晶体]|[T2] 灼烧', 9, 2, 0, 0)),
    (-8, ('[频率晶体]射频', 5, 0, 0, 0)),
    (-9, ('[频率晶体]微波', 4, 2, 0, 0)),
    (-10, ('[频率晶体]红外线', 5, 2, 0, 0)),
    (-11, ('[频率晶体]标准', 5, 3, 0, 0)),
    (-12, ('[频率晶体]紫外线', 6, 3, 0, 0)),
    (-13, ('[频率晶体]X射线', 6, 4, 0, 0)),
    (-14, ('[频率晶体]伽玛', 7, 4, 0, 0)),
    (-15, ('[频率晶体]多频', 7, 5, 0, 0)),
    (-16, ('[频率晶体]|[T2] 微光', 7, 7, 0, 0)),
    (-17, ('[频率晶体]|[T2] 爆燃', 7.7, 7.7, 0, 0)),
    # Different sizes of plasma do different damage ratios, the values here
    # are average of ratios across sizes
    (-18, ('[异种等离子]|[T2] 神秘', 0, 66319, 0, 33681)),
    (-19, ('[异种等离子]介子', 0, 60519, 0, 39481)),
    (-20, ('[异种等离子]重子', 0, 59737, 0, 40263)),
    (-21, ('[异种等离子]四重子', 0, 69208, 0, 30792)),
    (-22, ('[异种等离子]|[T2] 奥秘', 0, 55863, 0, 44137)),
    (-23, ('[混合弹药]|[T2] 钉刺', 0, 4, 4, 0)),
    (-24, ('[混合弹药]|[T2] 虚空', 0, 6, 5, 0)),
    (-25, ('[混合弹药]铁质轨道弹', 0, 2, 3, 0)),
    (-26, ('[混合弹药]钨质轨道弹', 0, 2, 4, 0)),
    (-27, ('[混合弹药]铱质轨道弹', 0, 3, 4, 0)),
    (-28, ('[混合弹药]铅质轨道弹', 0, 3, 5, 0)),
    (-29, ('[混合弹药]钍质轨道弹', 0, 4, 5, 0)),
    (-30, ('[混合弹药]钼质轨道弹', 0, 4, 6, 0)),
    (-31, ('[混合弹药]锰质轨道弹', 0, 5, 6, 0)),
    (-32, ('[混合弹药]反物质轨道弹', 0, 5, 7, 0)),
    (-33, ('[混合弹药]|[T2] 标枪', 0, 8, 6, 0)),
    (-34, ('[混合弹药]|[T2] 涅槃', 0, 7.7, 7.7, 0)),
    (-35, ('[射弹弹药]|[T2] 战栗', 0, 0, 3, 5)),
    (-36, ('[射弹弹药]|[T2] 雷暴', 0, 0, 5, 6)),
    (-37, ('[射弹弹药]碳铅弹', 0, 0, 4, 1)),
    (-38, ('[射弹弹药]核芯弹', 0, 0, 1, 4)),
    (-39, ('[射弹弹药]质子弹', 3, 0, 2, 0)),
    (-40, ('[射弹弹药]硬钼弹', 0, 3, 2, 3)),
    (-41, ('[射弹弹药]钛合金萨博弹', 0, 0, 6, 2)),
    (-42, ('[射弹弹药]电磁脉冲弹', 9, 0, 1, 2)),
    (-43, ('[射弹弹药]定相等离子弹', 0, 10, 2, 0)),
    (-44, ('[射弹弹药]聚变弹', 0, 0, 2, 10)),
    (-45, ('[射弹弹药]|[T2] 地震', 0, 0, 5, 9)),
    (-46, ('[射弹弹药]|[T2] 冰雹', 0, 0, 3.3, 12.1)),
    (-47, ('[导弹]雷神', 1, 0, 0, 0)),
    (-48, ('[导弹]炼狱', 0, 1, 0, 0)),
    (-49, ('[导弹]鞭挞', 0, 0, 1, 0)),
    (-50, ('[导弹]星爆', 0, 0, 0, 1)),
    (-51, ('[炸弹]电子炸弹', 6400, 0, 0, 0)),
    (-52, ('[炸弹]灼烧炸弹', 0, 6400, 0, 0)),
    (-53, ('[炸弹]震荡炸弹', 0, 0, 6400, 0)),
    (-54, ('[炸弹]榴霰炸弹', 0, 0, 0, 6400)),
    # Source: ticket #2067
    (-55, ('[NPC][深渊]全部', 130, 396, 258, 216)),
    (-56, ('[NPC][深渊]流浪者', 250, 250, 250, 250)),
    (-57, ('[NPC][深渊]无人机', 250, 250, 250, 250)),
    (-58, ('[NPC][深渊]巴希克深渊主宰', 0, 408, 592, 0)),
    (-59, ('[NPC][深渊]探索者', 406, 406, 94, 94)),
    (-60, ('[NPC][深渊]冬眠者', 313, 313, 187, 187)),
    (-61, ('[NPC][深渊]三神裔', 0, 610, 0, 390)),
    (-62, ('[NPC][小行星带]天使联合体', 1838, 562, 2215, 3838)),
    (-63, ('[NPC][小行星带]血袭者', 5067, 4214, 0, 0)),
    (-64, ('[NPC][小行星带]古斯塔斯', 0, 1828, 7413, 0)),
    (-65, ('[NPC][小行星带]自由无人机', 394, 666, 1090, 1687)),
    (-66, ('[NPC][小行星带]萨沙共和国', 5586, 4112, 0, 0)),
    (-67, ('[NPC][小行星带]天蛇', 0, 5373, 4813, 0)),
    (-68, ('[NPC][燃烧者]凝血级 (血袭者)', 90, 90, 0, 0)),
    (-69, ('[NPC][燃烧者]德拉米尔级 (天使)', 55, 0, 20, 96)),
    (-70, ('[NPC][燃烧者]夜魔侠级 (天蛇)', 0, 110, 154, 0)),
    (-71, ('[NPC][燃烧者]魔女级 (萨沙共和国)', 135, 30, 0, 0)),
    (-72, ('[NPC][燃烧者]潜龙级 (古斯塔斯)', 0, 0, 228, 0)),
    (-73, ('[NPC][燃烧者]恩尤级', 0, 147, 147, 0)),
    (-74, ('[NPC][燃烧者]战鹰级', 0, 0, 247, 0)),
    (-75, ('[NPC][燃烧者]美洲虎级', 36, 0, 50, 182)),
    (-76, ('[NPC][燃烧者]复仇级', 232, 0, 0, 0)),
    (-77, ('[NPC][燃烧者]阿什姆级 (血袭者)', 260, 100, 0, 0)),
    (-78, ('[NPC][燃烧者]塔洛斯级', 0, 413, 413, 0)),
    (-79, ('[NPC][燃烧者]哨兵级', 0, 75, 0, 90)),
    (-80, ('[NPC][死亡空间]天使联合体', 369, 533, 1395, 3302)),
    (-81, ('[NPC][死亡空间]血袭者', 6040, 5052, 10, 15)),
    (-82, ('[NPC][死亡空间]古斯塔斯', 0, 1531, 9680, 0)),
    (-83, ('[NPC][死亡空间]自由无人机', 276, 1071, 1069, 871)),
    (-84, ('[NPC][死亡空间]萨沙共和国', 3009, 2237, 0, 0)),
    (-85, ('[NPC][死亡空间]天蛇', 0, 3110, 1929, 0)),
    # Source: ticket #2067
    (-86, ('[NPC][入侵][Invading Precursor Entities]Dread', 0, 417, 0, 583)),
    (-87, ('[NPC][入侵][Invading Precursor Entities]Normal Subcaps', 0, 610, 0, 390)),
    (-88, ('[NPC][入侵][Invading Precursor Entities]Subcaps w/missiles 0% spool up', 367, 155, 367, 112)),
    (-89, ('[NPC][入侵][Invading Precursor Entities]Subcaps w/missiles 50% spool up', 291, 243, 291, 175)),
    (-90, ('[NPC][入侵][Invading Precursor Entities]Subcaps w/missiles 100% spool up', 241, 301, 241, 217)),
    (-91, ('[NPC][入侵][Retaliating Amarr Entities]Dread/Subcaps', 583, 417, 0, 0)),
    (-92, ('[NPC][入侵][Retaliating Caldari Entities]Dread', 1000, 0, 0, 0)),
    (-93, ('[NPC][入侵][Retaliating Caldari Entities]Subcaps', 511, 21, 29, 440)),
    (-94, ('[NPC][入侵][Retaliating Gallente Entities]Dread/Subcaps', 0, 417, 583, 0)),
    (-95, ('[NPC][入侵][Retaliating Minmatar Entities]Dread', 0, 0, 583, 417)),
    (-96, ('[NPC][入侵][Retaliating Minmatar Entities]Subcaps', 302, 136, 328, 234)),
    (-97, ('[NPC][任务]艾玛帝国', 4464, 3546, 97, 0)),
    (-98, ('[NPC][任务]加达里合众国', 0, 2139, 4867, 0)),
    (-99, ('[NPC][任务]统合部', 336, 134, 212, 412)),
    (-100, ('[NPC][任务]盖伦特联邦', 9, 3712, 2758, 0)),
    (-101, ('[NPC][任务]卡尼迪王国', 612, 483, 43, 6)),
    (-102, ('[NPC][任务]米玛塔尔共和国', 1024, 388, 1655, 4285)),
    (-103, ('[NPC][任务]莫德团', 25, 262, 625, 0)),
    (-104, ('[NPC][任务]图克尔部落', 0, 52, 10, 79)),
    (-105, ('[NPC]萨沙入侵', 1682, 1347, 3678, 3678)),
    (-106, ('[NPC]冬眠者', 1472, 1472, 1384, 1384))])


class DamagePattern:

    DAMAGE_TYPES = ('em', 'thermal', 'kinetic', 'explosive')
    _builtins = None

    def __init__(self, *args, **kwargs):
        self.builtin = False
        self.update(*args, **kwargs)

    @reconstructor
    def init(self):
        self.builtin = False

    def update(self, emAmount=25, thermalAmount=25, kineticAmount=25, explosiveAmount=25):
        self.emAmount = emAmount
        self.thermalAmount = thermalAmount
        self.kineticAmount = kineticAmount
        self.explosiveAmount = explosiveAmount

    @classmethod
    def getBuiltinList(cls):
        if cls._builtins is None:
            cls.__generateBuiltins()
        return list(cls._builtins.values())

    @classmethod
    def getBuiltinById(cls, id):
        if cls._builtins is None:
            cls.__generateBuiltins()
        return cls._builtins.get(id)

    @classmethod
    def getDefaultBuiltin(cls):
        if cls._builtins is None:
            cls.__generateBuiltins()
        return cls._builtins.get(-1)

    @classmethod
    def __generateBuiltins(cls):
        cls._builtins = OrderedDict()
        for id, (rawName, em, therm, kin, explo) in BUILTINS.items():
            pattern = DamagePattern(emAmount=em, thermalAmount=therm, kineticAmount=kin, explosiveAmount=explo)
            pattern.ID = id
            pattern.rawName = rawName
            pattern.builtin = True
            cls._builtins[id] = pattern

    def calculateEhp(self, fit):
        ehp = {}
        for (type, attr) in (('shield', 'shieldCapacity'), ('armor', 'armorHP'), ('hull', 'hp')):
            rawCapacity = fit.ship.getModifiedItemAttr(attr)
            ehp[type] = self.effectivify(fit, rawCapacity, type)

        return ehp

    def calculateEffectiveTank(self, fit, tankInfo):
        typeMap = {
            "passiveShield": "shield",
            "shieldRepair": "shield",
            "armorRepair": "armor",
            "armorRepairPreSpool": "armor",
            "armorRepairFullSpool": "armor",
            "hullRepair": "hull"}
        ereps = {}
        for field in tankInfo:
            if field in typeMap:
                ereps[field] = self.effectivify(fit, tankInfo[field], typeMap[field])
        return ereps

    def effectivify(self, fit, amount, type):
        type = type if type != "hull" else ""
        totalDamage = sum((self.emAmount, self.thermalAmount, self.kineticAmount, self.explosiveAmount))
        specificDivider = 0
        for damageType in self.DAMAGE_TYPES:
            # Compose an attribute name, then make sure the first letter is NOT capitalized
            attrName = "%s%sDamageResonance" % (type, damageType.capitalize())
            attrName = attrName[0].lower() + attrName[1:]

            resonance = fit.ship.getModifiedItemAttr(attrName)
            damage = getattr(self, "%sAmount" % damageType)

            specificDivider += damage / float(totalDamage or 1) * resonance

        return amount / (specificDivider or 1)

    importMap = {
        "em"   : "em",
        "therm": "thermal",
        "kin"  : "kinetic",
        "exp"  : "explosive"
    }

    @classmethod
    def oneType(cls, damageType, amount=100):
        pattern = DamagePattern()
        pattern.update(amount if damageType == "em" else 0,
                       amount if damageType == "thermal" else 0,
                       amount if damageType == "kinetic" else 0,
                       amount if damageType == "explosive" else 0)
        return pattern

    @classmethod
    def importPatterns(cls, text):
        lines = re.split('[\n\r]+', text)
        patterns = []
        numPatterns = 0

        # When we import damage profiles, we create new ones and update old ones. To do this, get a list of current
        # patterns to allow lookup
        lookup = {}
        current = eos.db.getDamagePatternList()
        for pattern in current:
            lookup[pattern.rawName] = pattern

        for line in lines:
            try:
                if line.strip()[0] == "#":  # comments
                    continue
                line = line.split('#', 1)[0]  # allows for comments
                type, data = line.rsplit('=', 1)
                type, data = type.strip(), data.split(',')
            except (KeyboardInterrupt, SystemExit):
                raise
            except:
                # Data isn't in correct format, continue to next line
                continue

            if type != "DamageProfile":
                continue

            numPatterns += 1
            name, data = data[0], data[1:5]
            fields = {}

            for index, val in enumerate(data):
                try:
                    fields["%sAmount" % cls.DAMAGE_TYPES[index]] = int(val)
                except (KeyboardInterrupt, SystemExit):
                    raise
                except:
                    continue

            if len(fields) == 4:  # Avoid possible blank lines
                if name.strip() in lookup:
                    pattern = lookup[name.strip()]
                    pattern.update(**fields)
                    eos.db.save(pattern)
                else:
                    pattern = DamagePattern(**fields)
                    pattern.rawName = name.strip()
                    eos.db.save(pattern)
                patterns.append(pattern)

        eos.db.commit()

        return patterns, numPatterns

    EXPORT_FORMAT = "DamageProfile = %s,%d,%d,%d,%d\n"

    @classmethod
    def exportPatterns(cls, *patterns):
        out = "# Exported from pyfa\n#\n"
        out += "# Values are in following format:\n"
        out += "# DamageProfile = [name],[EM amount],[Thermal amount],[Kinetic amount],[Explosive amount]\n\n"
        for dp in patterns:
            out += cls.EXPORT_FORMAT % (dp.rawName, dp.emAmount, dp.thermalAmount, dp.kineticAmount, dp.explosiveAmount)

        return out.strip()

    @property
    def name(self):
        return self.rawName

    @property
    def fullName(self):
        categories, tail = self.__parseRawName()
        return '{}{}'.format(''.join('[{}]'.format(c) for c in categories), tail)

    @property
    def shortName(self):
        return self.__parseRawName()[1]

    @property
    def hierarchy(self):
        return self.__parseRawName()[0]

    def __parseRawName(self):
        categories = []
        remainingName = self.rawName.strip() if self.rawName else ''
        while True:
            start, end = remainingName.find('['), remainingName.find(']')
            if start == -1 or end == -1:
                return categories, remainingName
            splitter = remainingName.find('|')
            if splitter != -1 and splitter == start - 1:
                return categories, remainingName[1:]
            categories.append(remainingName[start + 1:end])
            remainingName = remainingName[end + 1:].strip()

    def __deepcopy__(self, memo):
        p = DamagePattern(self.emAmount, self.thermalAmount, self.kineticAmount, self.explosiveAmount)
        p.rawName = "%s copy" % self.rawName
        return p
